/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;

class Fastarandom2 {
    public static long timeInUs() {
        return System.nanoTime();
    }
    // Modify n value to change the difficulty of the benchmark

    public static int input = 250000;

    public static class AminoAcid {
        double prob;
        int sym; 
        public AminoAcid(double prob, int sym) {
            this.prob = prob;
            this.sym = sym;
        }
    }

    public static final int IM = 139968;
    public static final int IA = 3877;
    public static final int IC = 29573;
    public static final int SEED = 42;

    public static int n = input;
    public static int width = 60;

    public AminoAcid[] iub = {
            new AminoAcid(0.27, 97), // "a"),
            new AminoAcid(0.12, 99), // "c"),
            new AminoAcid(0.12, 103), // "g"),
            new AminoAcid(0.27, 116), // "t"),
            new AminoAcid(0.02, 66), // "B"),
            new AminoAcid(0.02, 68), // "D"),
            new AminoAcid(0.02, 72), // "H"),
            new AminoAcid(0.02, 75), // "K"),
            new AminoAcid(0.02, 77), // "M"),
            new AminoAcid(0.02, 78), // "N"),
            new AminoAcid(0.02, 82), // "R"),
            new AminoAcid(0.02, 83), // "S"),
            new AminoAcid(0.02, 86), // "V"),
            new AminoAcid(0.02, 87), // "W"),
            new AminoAcid(0.02, 89), // "Y"),
    };

    public static int binarySearch(double rnd, AminoAcid[] arr) {
        int low = 0;
        int high = arr.length - 1;
        while (low <= high) {
            int middle = (low + high) >>> 1;
            if (arr[middle].prob >= rnd) {
                high = middle - 1;
            } else {
                low = middle + 1;
            }
        }
        return arr[high + 1].sym;
    }

    public static void accumulateProbabilities(AminoAcid[] acid) {
        for (int i = 1; i < acid.length; i++) {
            acid[i].prob += acid[i-1].prob;
        }
    }

    public static void randomFasta(ArrayList<Integer> buffer, AminoAcid[] acid, int n) {
        int cnt = n;
        accumulateProbabilities(acid);
        int pos = 0;
        int seed = SEED;
        while (cnt > 0) {
            int m = cnt > width ? width : cnt;
            double f = 1.0 / IM;
            int myrand = seed;
            for (int i = 0; i < m; i++) {
                myrand = (myrand * IA + IC) % IM;
                double r = myrand * f;
                buffer.set(pos, binarySearch(r, acid));
                pos++;
                if (pos == buffer.size()) {
                    pos = 0;
                }
            }
            seed = myrand;
            buffer.set(pos, 10);
            pos++;
            if (pos == buffer.size()) {
                pos = 0;
            }
            cnt -= m;
        }
        //System.out.println("seed " + seed);
        //System.out.println("pos " + pos);
    }

}
/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
    public static void main(String[] args) {
        runFastaRandom2();
    }
   /*
    * @Benchmark
    */
    public static double runFastaRandom2() {
       int bufferSize = 256 * 1024;
       ArrayList<Integer> buffer = new ArrayList<>();
       for (int i = 0; i < bufferSize; i++) {
            buffer.add(10);
       }
       Fastarandom2.AminoAcid[] homosapiens = {
               new Fastarandom2.AminoAcid(0.3029549426680, 97), // "a"),
               new Fastarandom2.AminoAcid(0.1979883004921, 99), // "c"),
               new Fastarandom2.AminoAcid(0.1975473066391, 103), // "g"),
               new Fastarandom2.AminoAcid(0.3015094502008, 116), // "t"),
       };
       long start = Fastarandom2.timeInUs();
       Fastarandom2.randomFasta(buffer, homosapiens, 5*Fastarandom2.n);
       long end = Fastarandom2.timeInUs();
       double time = (end - start);
       System.out.println("res:" + buffer.get(0));
       System.out.println("Array Access - RunFastaRandom2:" + time / 1_000_000.0 + "ms");
       return time;
    }
}